import java.io.* ;
import java.net.* ;
import java.util.* ;

public class Server {
   ServerSocket		svrSocket;
   Broadcaster		broadcaster = new Broadcaster();

   Server() { }

   public static void main( String arg[] ) {
	Server fm =  new Server() ;
	
	try {
			fm.svrSocket = new ServerSocket(5555); 
		}
	catch (IOException e)
	{
		e.printStackTrace(); 
	}

	while (true) {
	    try {
		Socket chaterSocket = fm.svrSocket.accept() ;
		new Agent(fm.broadcaster, chaterSocket).start();
	    }
	    catch (IOException e) { e.printStackTrace(); }
	}
  }
}

class Agent extends Thread {
   Socket			chaterSocket;
   ObjectInputStream		in;
   ObjectOutputStream		out;
   Visitor			visitor = null;
   Broadcaster			bctr;
   Message			message = null ;

   public Agent(Broadcaster b, Socket s)
   {
	   bctr = b; chaterSocket = s; 
	   try {
				in  = new ObjectInputStream (chaterSocket.getInputStream());
				out  = new ObjectOutputStream (chaterSocket.getOutputStream());
				visitor = new Visitor( out );
				bctr.visitors.add(visitor);
			} 
	   catch (IOException e ){ e.printStackTrace(); }
   }

   public void run() {
	while ( true ) {
	try 
	{
		try 
		{
		    message = (Message) in.readObject();
		} 
		catch (ClassNotFoundException nfd) { nfd.printStackTrace(); }
		if ( message == null ) { chaterSocket.close(); return ; }
		switch ( message.type ) {
		    case Message.LOGIN :
		      // In case of login and logout, message a visistor's
		      // name. The server will combine "login" and "logout"
		      // with visitor's name to send to every one.
		      visitor.name = new String (message.message) ;
		      Collections.sort(bctr.visitors);
		      message.receivers = bctr.getReceiverNames();
		      // Visitor is added to vector already.
		      bctr.broadcast(message);
		      break;
		
		    // Remove visitor from list. Vector is thread safe.
		    case Message.LOGOUT: 
		    	bctr.visitors.remove( visitor );
		    	try { 
		       		if ( visitor != null && visitor.out != null ) {
				    visitor.out.close(); visitor = null;
				}
		       		if ( chaterSocket != null ) chaterSocket.close();
		        } catch (IOException e) {}
			bctr.broadcast(message);
			break;
		    case Message.PRIVATE:
			// get a list of visitors from its private list, and boradcast.
			System.out.printf("Private Message Received: %s\n", message);
			bctr.broadcastToSelected(message);
			break;
		    case Message.PUBLIC:
			bctr.broadcast(message);
			break;
		}
				// If here is an error in any broadcast, remove visitor.
	   } catch (IOException e) { bctr.visitors.remove(visitor); return ;}
	}
   }
}

class Broadcaster {
   public Vector<Visitor>	visitors = new Vector<Visitor>(10);
   public Broadcaster( ) { }

   public synchronized void broadcast( Message message ) {
	Iterator<Visitor>  itr = visitors.iterator();
	ObjectOutputStream out ;
	if( message == null ) return;
	while ( itr.hasNext() ) {
	   try {
		out = itr.next().out;
		out.writeObject(message);
		out.flush();
	   } catch (IOException e) {}
	}
	System.out.printf("\nMessage-" + message +
		"- broadcasted to %d visitors.", visitors.size());
   }

   public synchronized void broadcastToSelected( Message message ) {
	if( message == null || visitors == null ) return;
	Vector<String> selectedNames = message.getReceiverNames();
	if ( selectedNames == null ) return;
	Iterator<Visitor>  itr = visitors.iterator();
	Iterator<String>   nameItr = selectedNames.iterator();
	Visitor  v = null;
	String   name = null;
	int      k,  nameMatched, count = 0;
	while ( nameItr.hasNext() ) {
	   name = nameItr.next();
	   do {
		if ( ! itr.hasNext() ) return;
		v = itr.next();
		k = name.compareTo( v.name ) ;
	   } while ( k > 0 );
	   if ( k == 0 )
	   try {
		v.out.writeObject(message);
		v.out.flush();
		count++;
	    } catch (IOException e) { }
	 }
         System.out.printf("\n%s -broadcasted to %d visitors.", message, count);
   }

   public String getReceiverNames() {
       StringBuffer names = new StringBuffer( 200 );
       Iterator<Visitor> itr = visitors.iterator();
       while ( itr.hasNext() ) names.append( itr.next().name + Message.NAME_SEPARATOR );
       System.out.printf("visitors after new login: " + names);
       return names.toString().substring(0, names.length() - 1 );
   }
}
